import json
import re
from common.settings import logger
from common.constant import ApiInfo, AssertType
from typing import Union, Dict, List
from jsonpath import jsonpath


class AssertHandler:

    def __init__(self, api_info: Dict, actual_result: str, check_point: Dict):
        self.api_info = api_info
        self.actual_result = actual_result
        self.check_point = check_point

    @property
    def assert_info(self) -> Union[Dict, None]:
        return self.api_info.get(ApiInfo.ASSERT_INFO.value)

    @property
    def json_full_text(self) -> Union[str, None]:
        return self.check_point.get(AssertType.JSON_FULL_TEXT.value)

    @property
    def gte_amount(self) -> Union[int, None]:
        return self.check_point.get(AssertType.GTE_AMOUNT.value)

    @property
    def equal_amount(self) -> Union[int, None]:
        return self.check_point.get(AssertType.EQUAL_AMOUNT.value)

    @property
    def contain_fields(self) -> Union[str, None]:
        return self.check_point.get(AssertType.CONTAIN_FIELDS.value)

    @property
    def equal_field(self) -> Union[str, None]:
        return self.check_point.get(AssertType.EQUAL_FIELD.value)

    @staticmethod
    def remove_response_field(response, ignore_json_path: str) -> Dict:
        # ignore_json_path 必须以"$."开始，后面跟着一系列由字母和数字组成的子字符串，并由"."分隔
        pattern = r'^\$\.[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*$'
        if re.match(pattern, ignore_json_path):
            ignore_json_path = ignore_json_path.split('.')
            key = ''
            for path in ignore_json_path[1:]:
                key += f"['{path}']"
            del_statement = f"del(response{key})"
            exec(del_statement)
            return response
        else:
            raise ValueError(f'ignoreResponseKey: {ignore_json_path}有误')

    def json_full_text_compare(self) -> None:
        actual_response = json.loads(self.actual_result)
        expected_response = json.loads(self.json_full_text)

        # 用于删除一些每次请求都会发生变化的响应值，如：traceId、时间戳等
        ignore_field = self.assert_info.get(ApiInfo.JSON_FULL_TEXT_IGNORE_FIELD.value)
        if ignore_field:
            actual_response = self.remove_response_field(actual_response, ignore_field)
            expected_response = self.remove_response_field(expected_response, ignore_field)

        assert actual_response == expected_response, logger.error('json全文校验未通过')
        logger.info('json全文检验通过')

    def get_json_path_value(self, key: str, assert_type: str) -> Union[List[str], str, int]:
        json_path = self.assert_info.get(key)
        if json_path:
            # 校验包含指定字段的值，获取jsonpath对应的列表，获取列表中所有指定字段，把它们添加到一个列表中并返回
            if assert_type == AssertType.CONTAIN_FIELDS.value:
                value_lst = []
                json_path_and_field = json_path.split(',')
                if len(json_path_and_field) > 2:
                    raise ValueError('只支持一个指定字段')
                json_path, field = json_path_and_field
                # 删除空格
                field = field.strip()
                res = jsonpath(json.loads(self.actual_result), json_path)
                for item in res[0]:
                    value = item.get(field)
                    if not isinstance(value, str):
                        value = str(value)
                    value_lst.append(value)
                return value_lst
            else:
                res = jsonpath(json.loads(self.actual_result), json_path)
                # 校验等于指定字段的值
                if assert_type == AssertType.EQUAL_FIELD.value:
                    value = res[0]
                    if not isinstance(value, str):
                        value = str(value)
                    return value
                # 校验数据条数，获取jsonpath对应的列表，返回它的长度
                elif assert_type in (AssertType.GTE_AMOUNT.value, AssertType.EQUAL_AMOUNT.value):
                    return len(res[0])
        else:
            raise ValueError(f'{key}为空')

    def gte_amount_compare(self) -> None:
        length = self.get_json_path_value(ApiInfo.LIST_JSON_PATH.value, AssertType.GTE_AMOUNT.value)
        assert length > self.gte_amount or length == self.gte_amount, logger.error('数据条数大于等于预期结果校验未通过')
        logger.info('数据条数大于等于预期结果校验通过')

    def equal_amount_compare(self) -> None:
        length = self.get_json_path_value(ApiInfo.LIST_JSON_PATH.value, AssertType.EQUAL_AMOUNT.value)
        assert length == self.equal_amount, logger.error('数据条数等于预期结果校验未通过')
        logger.info('数据条数等于预期结果校验通过')

    def contain_fields_compare(self) -> None:
        value_lst = self.get_json_path_value(ApiInfo.CONTAIN_FIELDS_JSON_PATH.value, AssertType.CONTAIN_FIELDS.value)
        # excel中填写了多个值
        if ',' in self.contain_fields:
            fields_lst = self.contain_fields.split(',')
            assert set(fields_lst).issubset(set(value_lst)), logger.error('包含指定字段的值校验未通过')
            logger.info('包含指定字段的值校验通过')
        # excel中填写了单个值
        else:
            assert self.contain_fields in value_lst, logger.error('包含指定字段的值校验未通过')
            logger.info('包含指定字段的值校验通过')

    def equal_field_compare(self) -> None:
        value = self.get_json_path_value(ApiInfo.EQUAL_FIELD_JSON_PATH.value, AssertType.EQUAL_FIELD.value)
        assert self.equal_field == value, logger.error('等于指定字段的值校验未通过')
        logger.info('等于指定字段的值通过')

    def run(self):
        if self.json_full_text:
            self.json_full_text_compare()
        if self.gte_amount:
            self.gte_amount_compare()
        if self.equal_amount:
            self.equal_amount_compare()
        if self.contain_fields:
            self.contain_fields_compare()
        if self.equal_field:
            self.equal_field_compare()
